﻿using DXBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace DXBase
{
	/// <summary>
	/// A base class for strongly typed pointer, so that they can be used in untyped context.
	/// </summary>
	public class Pointer
	{
		public Pointer()
		{
			Native = new MemoryBlock();
			IsPointerCached = true;
		}
		public Pointer(IPointer value)
		{
			if (value == null)
				throw new ArgumentNullException();
			nptr = value;
			IsPointerCached = true;
			UpdateCache();
		}

		public IPointer Native
		{
			get { return nptr; }
			set
			{
				if (value == null)
					throw new ArgumentNullException();
				nptr = value;
				pCached = GetDataPtr();
			}
		}
		IPointer nptr;

		IntPtr GetDataPtr() { return (IntPtr)Native.Data; }

		/// <summary>
		/// When the pointer is cached the read write access operation are greatly improved.
		/// However this might led to memory corruption if some other code resize the underlying pointer.
		/// Default value is true.
		/// </summary>
		public bool IsPointerCached
		{
			get { return isPCached; }
			set
			{
				isPCached = value;
				UpdateCache();
			}
		}
		bool isPCached = false;
		IntPtr pCached;

		/// <summary>
		/// Return Data or a cached pointer when <see cref="IsPointerCached"/> is <c>true</c>.
		/// </summary>
		public IntPtr GetPointer() { return isPCached ? pCached : GetDataPtr(); }
		/// <summary>
		/// If the pointer has been updated by other code, this will update the pointer cache
		/// </summary>
		public virtual void UpdateCache() { pCached = GetDataPtr(); }
	}

	/// <summary>
	/// A list item with an underlying native pointer as backing storage.
	/// Beware this item is unsafe, no bounds checking on many methods.
	/// The main purpose of this class is to let the C# code define which buffer data will be used,
	/// as opposed to this being imposed by the API.
	/// </summary>
	/// <remarks>Generic and pointer are not supported together by the compiler.</remarks>
	/// <typeparam name="T">Must be a primitive type, an enum, a WinRT value, a StructLayout structure.</typeparam>
	public abstract class Pointer<T> : Pointer, IList<T>
		where T : struct
	{
		public Pointer()
		{
		}
		public Pointer(IPointer p) : base(p)
		{
			size = (int)(p.ByteSize / SizeOfType);
		}

		int size = 0;

		/// <summary>
		/// Reduce the capacity to equals the size, to reduce memory waste, if possible.
		/// </summary>
		public void Trim()
		{
			if (Native is MemoryBlock)
				Capacity = Count;
		}

		public static int SizeOfType { get { return sizeoft; } }
		static readonly int sizeoft = Marshal.SizeOf(typeof(T));

		#region core methods (override those in your own types)

		// this one is mandatory
		public abstract T this[int index]
		{
			get;
			set;
		}

		// those ones can be made faster
		protected virtual void CopyRangeAt(int index, ICollection<T> items)
		{
			foreach (var value in items)
				this[index++] = value;
		}

		public virtual int IndexOf(T item)
		{
			for (int i = 0, N = Count; i < N; i++)
				if (Equals(this[i], item))
					return i;
			return -1;
		}

		protected virtual void CopyToImpl(T[] array, int arrayIndex, int N)
		{
			for (int i = 0; i < N; i++)
				array[i] = this[arrayIndex++];
		}

		public void InsertRange(int index, ICollection<T> items)
		{
			if (items == null || items.Count == 0)
				return;
			NInsert(index, items.Count);
			CopyRangeAt(index, items);
		}

		public int CopyTo(T[] array, int arrayIndex)
		{
			if (array == null || array.Length == 0)
				return 0;
			if (arrayIndex < 0)
				throw new ArgumentOutOfRangeException();
			if (arrayIndex >= Count)
				return 0;
			int N = array.Length;
			if (arrayIndex + N > Count)
				N = Count - arrayIndex;
			CopyToImpl(array, arrayIndex, N);
			return N;
		}

		#endregion

		#region a few pointers extra

		protected void NInsert(int offset, int count)
		{
			if (count < 0)
				throw new ArgumentException();
			Count += count;
			Copy(offset, offset + count, count);
		}
		protected void NRemove(int offset, int count)
		{
			if (count < 0)
				throw new ArgumentException();
			if (offset + count > Count)
				throw new ArgumentException();
			Count -= count;
			Copy(offset + count, offset, count);
		}
		void Copy(int src, int dst, int count)
		{
			src *= SizeOfType;
			dst *= SizeOfType;
			count *= SizeOfType;
			MemoryBlock.Memcopy(Native, (uint)src, Native, (uint)dst, (uint)count);
		}

		public void Add(params T[] items) { AddRange(items); }
		public void AddRange(ICollection<T> items) { InsertRange(Count, items); }
		public void Insert(int index, params T[] items) { InsertRange(index, items);  }
		public void RemoveRange(int index, int count) { NRemove(index, count); }

		#endregion

		#region vanilla IList<T>

		public void Insert(int index, T item)
		{
			NInsert(index, 1);
			this[index] = item;
		}

		public void RemoveAt(int index) { RemoveRange(index, 1); }

		public void Add(T item) { Insert(Count, item); }

		public void Clear() { Count = 0; }

		public bool Contains(T item) { return IndexOf(item) != -1; }

		int ICollection<T>.Count { get { return Count; } }
		public virtual int Count
		{
			get { return size; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();

				if (value > Capacity)
				{
					const int Pow2 = 12;
					if (value > 1<<Pow2)
					{
						Capacity = value - (value % (1 << 10)) + (1 << 10);
					}
					else
					{
						for(int i=4; i<(Pow2+2); i++)
						{
							if (value < (1<<i))
							{
								Capacity = 1 << i;
								break;
							}
						}
					}
				}
				size = value;
			}
		}
		public virtual int Capacity
		{
			get { return (int)(Native.ByteSize / SizeOfType); }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();
				var mb = Native as MemoryBlock;
				if (mb == null)
					throw new InvalidOperationException("Can't change the capacity");

				mb.ByteSize = (uint)(value * SizeOfType);
				UpdateCache();

				if (Capacity > Count)
					Count = Capacity;
			}
		}

		bool ICollection<T>.IsReadOnly { get { return false; } }

		bool ICollection<T>.Remove(T item)
		{
			int index = IndexOf(item);
			if (index != -1)
			{
				RemoveAt(index);
				return true;
			}
			return false;
		}
		void ICollection<T>.CopyTo(T[] array, int arrayIndex) { CopyTo(array, arrayIndex); }

		public IEnumerator<T> GetEnumerator()
		{
			for (int i = 0; i < Count; i++)
				yield return this[i];
		}
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }

		#endregion
	}
}
